# RainSynth (Copyright (c) 2009, David Townshend All rights reserved.)
# See COPYING for license details

import csv
import math
from datetime import datetime, timedelta
from copy import copy

def _tominutes(d):
    return ((d.days * 24) + d.hours * 60) + d.minutes

class FileOptions:
    '''
    A group of parameters which define the format of a timeseries file.

    The instance variables detailed below are used to define the format of a
    file. They can be set by passing them as keyword arguments to the
    constructor, by passing them as keyword arguments to the `set` function, or
    by setting them as class properties.

    For example, set the number of header rows to 3 cound be done in three ways:

    .. python::
        opt = FileOptions(headrows=3)
        opt.set(headrows=3)
        opt.headrows = 3

    :IVariables:
        columns : ``string list = ['Date', 'Time', 'Value']``
            The order of fields in the file. The list should contain only a subset
            of the strings ``['Time, 'Date', 'DateTime', 'Value', 'Ignore']``. Any
            unrecognised values will be treated as 'Ignore'.

        delimiter : ``string = '\\t'``
            The field separator in the file.
            It is usually one of ``' '`` (space), ``','`` (comma) or ``'\\t'`` (tab).

        mergedelimiters : ``boolean = False``
            Controls whether two or more adjacent delimiters should be treated as one.

        quotechar : ``string = '"'``
            The ascii character which is used for quoted fields. A common value is
            '"', although it would usually not be used in a timeseries file since
            most of the data is numerical.

        headrows : ``integer = 0``
            The number of header rows in the file which are to be ignored.

        dateformat : ``string = '%d/%m/%Y'``
            For 'Date' and 'DateTime' columns, the date (and time) format according
            to the C standard (1989) format codes.

        timeformat : ``string = '%H:%M:%S'``
            The format of the 'Time' field, using the same system as C{dateformat}.
    '''

    def __init__(self, **kwargs):
        '''Constructor

        Any combination of the `FileOptions` instance parameters can be set by
        passing them as keyword arguments.
        '''
        self.delimiter = '\t'
        self.mergedelimiters = False
        self.quotechar = '"'
        self.headrows = 0
        self.columns = ['Date', 'Time', 'Value']
        # These are according to the format codes that the C standard (1989 version) requires
        self.dateformat = '%d/%m/%Y'    # This variable is also used for column == 'datetime'
        self.timeformat = '%H:%M:%S'
        self.set(kwargs)

    def set(self, **kwargs):
        '''Set any number of instance variables.'''
        for arg, value in kwargs.iteritems():
            setattr(self, arg, value)


class Timeseries(object):

    '''
    Representation of a rainfall timeseries.

    The timeseries is defined by a starting time and date, a resolution (i.e.
    timestep between consecutive values) and an array of values.
    '''

    def __init__(self, data=None, resolution=0, start=None):
        '''
        Constructor

        :Parameters:
            data : list
                An ordered list of values in the timeseries.
            resolution : integer
                The number of minutes between consecutive values in L{data}.
            start : datetime
                The time and date of the first value in L{data}.
        '''
        self.data = [] if data is None else data
        self.resolution = resolution  # In minutes
        self.start = datetime() if start is None else start

    def readfile(self, filename, options=None, **kwargs):
        '''
        Load a timerseries from a file

        :Parameters:
            filename : string
                The URI to the file to be opened. It should be a valid path.
            options
                Either a `FileOptions` object or keyword arguments as used in the
                `FileOptions` constructor.
        '''
        # Get options
        if options is None:
            options = FileOptions(kwargs)
        else:
            options.set(kwargs)

        # Open file
        file = csv.reader(open(filename, 'rb'), delimiter=options.delimiter, quotchar=options.quotechar)

        lastdt = None
        for line in file[options.headrows:]:
            dt = datetime()
            value = 0
            for i, col in enumerate(options.columns):
                if col == 'Date':
                    date = datetime.strptime(line[i], options.dateformat)
                    dt.year, dt.month, dt.day = date.year, date.month, date.day
                elif col == 'Time':
                    time = datetime.strptime(line[i], options.timeformat)
                    # Don't work to a resolution higher than 1 minute
                    dt.hours, dt.minutes = time.hours, time.minutes
                elif col == 'DateTime':
                    dt = datetime.strptime(line[i], options.dateformat)
                elif col == 'Value':
                    value = line[i]
            if lastdt is None:
                lastdt = dt
                self.start = dt
            elif lastdt == self.start:
                self.resolution = tominutes(dt - lastdt)
            else:
                res = tominutes(dt - lastdt)
                if res % self.resolution == 0:
                    self.data = self.data + [0] * ((res / self.resolution) - 1) + [value]
                else:
                    assert res == self.resolution, 'Line %i: Resolution should be %i' % (len(data) - 1, resolution)

    def aggregate(factor):
        ''' Aggregate a timeseries into intervals (in minutes) and return the result. The interval should be a integral
        multiple of the current timeseries resolution.'''
        assert factor > 0 and factor == int(Factor)
        return Timeseries(
            [sum(self.data[i:i+factor]) for i in range(math.ceil(len(self.data) / factor))],
            self.start,
            self.resolution*factor)

